package com.google.gwt.sample.hello.client.notgenerated.eventFramework;

/**
 * A Property wraps a named value such that it can generate
 * PropertyChangeEvents. Since a common use-case for Property is to bind them so
 * that a change of one Property causes the update of another, Property is also
 * an PropertyChangeListener which calls the set() method if it gets an update
 * event.
 * 
 * @param <T>
 *            type of field that Property wraps.
 */
public class Property<T> implements PropertyChangeListener<T> {
	private PropertyChangeSupport<T> changes = new PropertyChangeSupport<T>(
			this);

	public PropertyChangeSupport<T> getChanges() {
		return changes;
	}

	private String name;
	private Accessor<T> accessor;

	public Accessor<T> getAccessor() {
		return accessor;
	}

	/**
	 * A helper method that binds the source to the target, and vice-versa, so
	 * that a change either results in the same change to the other. A side
	 * effect of the binding is that the target's value is set to the source's
	 * value.
	 * 
	 * @param source
	 * @param target
	 */
	public static void bindTwoWay(Property<?> source, Property<?> target) {
		target.bind(source);
		source.bind(target);
	}

	/**
	 * Constructor.
	 * 
	 * @param name
	 *            String name for this property
	 * @param accessor
	 *            to wrap underlying field
	 */
	public Property(String name, Accessor<T> accessor) {
		this.name = name;
		this.accessor = accessor;
	}

	public String getName() {
		return name;
	}

	public T get() {
		return accessor.get();
	}

	/**
	 * The semantic of the set method should be to change the wrapped value ONLY
	 * if the newValue is different from the current value, and to fire a
	 * PropertyChangeEvent if the wrapped value is updated.
	 * 
	 * @param newValue
	 */
	public void set(T newValue) {
		if (accessor.get() == null || !accessor.get().equals(newValue)) {
			T oldValue = accessor.get();
			accessor.set(newValue);
			firePropertyChangeEvent(oldValue, newValue);
		}
	}

	/**
	 * Binds this Property to changes from the passed Property. As a side
	 * effect, this Property's value is set to the passed Property's value.
	 * 
	 * @param source
	 */
	@SuppressWarnings("unchecked")
	public void bind(Property source) {
		set((T) source.get());
		source.addPropertyChangeListener(this);
	}

	public void addPropertyChangeListener(PropertyChangeListener<T> listener) {
		changes.addPropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(PropertyChangeListener<T> listener) {
		changes.removePropertyChangeListener(listener);
	}

	public void propertyChanged(PropertyChangeEvent<T> changeEvent) {
		set(changeEvent.getNewValue());
	}

	protected void firePropertyChangeEvent(T oldValue, T newValue) {
		changes.firePropertyChange(oldValue, newValue);
	}

	@Override
	public String toString() {
		return "Property[" + getName() + ":" + get() + "]";
	}

	/**
	 * @return true if the value of this Property is the same as the value of
	 *         the passed Property
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Property) {
			Property<T> other = (Property<T>) obj;
			return get().equals(other.get());
		}
		return false;
	}
}
